﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SpectralCamera.Extend
{
    public sealed class TcpHelper : IDisposable
    {
        #region Server Implementation
        public class TcpServer : IDisposable
        {
            private TcpListener _listener;
            private CancellationTokenSource _cts;
            private ConcurrentBag<ClientConnection> _connections = new ConcurrentBag<ClientConnection>();

            public event EventHandler<ClientConnectedEventArgs> ClientConnected;
            public event EventHandler<DataReceivedEventArgs> DataReceived;
            public event EventHandler<Exception> ErrorOccurred;

            public bool IsRunning => _listener?.Server.IsBound ?? false;

            public async Task StartAsync(IPAddress ip, int port, int backlog = 10)
            {
                if (IsRunning) return;

                try
                {
                    _cts = new CancellationTokenSource();
                    _listener = new TcpListener(ip, port);
                    _listener.Start(backlog);
                    _ = AcceptConnectionsAsync(_cts.Token);
                }
                catch (Exception ex)
                {
                    OnError(ex);
                    throw;
                }
            }

            private async Task AcceptConnectionsAsync(CancellationToken ct)
            {
                while (!ct.IsCancellationRequested)
                {
                    try
                    {
                        var systemClient = await _listener.AcceptTcpClientAsync().ConfigureAwait(false);
                        var connection = new ClientConnection(systemClient);
                        _connections.Add(connection);
                        OnClientConnected(new ClientConnectedEventArgs(connection));

                        _ = HandleConnectionAsync(connection, ct);
                    }
                    catch (ObjectDisposedException) { /* Listener closed */ }
                    catch (SocketException ex) when (ex.SocketErrorCode == SocketError.Interrupted) { /* Cancelled */ }
                    catch (Exception ex)
                    {
                        OnError(ex);
                    }
                }
            }

            private async Task HandleConnectionAsync(ClientConnection connection, CancellationToken ct)
            {
                try
                {
                    using (connection)
                    using (var stream = connection.SystemClient.GetStream())
                    {
                        var buffer = new byte[4096];
                        while (!ct.IsCancellationRequested && connection.SystemClient.Connected)
                        {
                            var bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, ct);
                            if (bytesRead == 0) break;

                            var data = new byte[bytesRead];
                            Array.Copy(buffer, data, bytesRead);
                            OnDataReceived(new DataReceivedEventArgs(connection, data));
                        }
                    }
                }
                catch (Exception ex)
                {
                    OnError(ex);
                }
                finally
                {
                    RemoveConnection(connection);
                }
            }

            private void RemoveConnection(ClientConnection connection)
            {
                var newBag = new ConcurrentBag<ClientConnection>();
                while (_connections.TryTake(out var item))
                {
                    if (!ReferenceEquals(item, connection))
                    {
                        newBag.Add(item);
                    }
                }
                _connections = newBag;
            }

            public async Task SendAsync(ClientConnection connection, byte[] data)
            {
                if (connection?.SystemClient.Connected != true) return;

                try
                {
                    await connection.SystemClient.GetStream().WriteAsync(data, 0, data.Length);
                }
                catch (Exception ex)
                {
                    OnError(ex);
                }
            }

            public void Stop()
            {
                try
                {
                    _cts?.Cancel();
                    _listener?.Stop();

                    // 线程安全的集合清理
                    var oldConnections = Interlocked.Exchange(
                        ref _connections,
                        new ConcurrentBag<ClientConnection>()
                    );

                    foreach (var conn in oldConnections)
                    {
                        try
                        {
                            conn.Dispose();
                        }
                        catch { /* 忽略释放异常 */ }
                    }
                }
                catch (Exception ex)
                {
                    OnError(ex);
                }
            }

            private void OnClientConnected(ClientConnectedEventArgs e) => ClientConnected?.Invoke(this, e);
            private void OnDataReceived(DataReceivedEventArgs e) => DataReceived?.Invoke(this, e);
            private void OnError(Exception ex) => ErrorOccurred?.Invoke(this, ex);

            public void Dispose() => Stop();
        }

        public class ClientConnection : IDisposable
        {
            public TcpClient SystemClient { get; }
            public IPEndPoint RemoteEndPoint => (IPEndPoint)SystemClient.Client.RemoteEndPoint;

            public ClientConnection(TcpClient client)
            {
                SystemClient = client ?? throw new ArgumentNullException(nameof(client));
            }

            public void Dispose()
            {
                try
                {
                    SystemClient.Dispose();
                }
                catch { /* 忽略释放异常 */ }
            }
        }
        #endregion

        #region Client Implementation
        public class TcpClientHelper : IDisposable
        {
            private TcpClient _systemClient;
            private CancellationTokenSource _cts;
            private NetworkStream _stream;

            public event EventHandler<DataReceivedEventArgs> DataReceived;
            public event EventHandler<Exception> ErrorOccurred;

            public bool IsConnected => _systemClient?.Connected == true;
            public IPEndPoint RemoteEndPoint => (IPEndPoint)(_systemClient?.Client?.RemoteEndPoint);

            public async Task ConnectAsync(IPAddress ip, int port, int timeout = 5000)
            {
                if (IsConnected) return;

                try
                {
                    IPEndPoint serverIPEndPoint = new IPEndPoint(IPAddress.Parse("192.168.0.4"), port);
                    Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    socket.Connect(serverIPEndPoint); //配置服务器IP与端口

                    _systemClient = new TcpClient(new IPEndPoint(IPAddress.Parse("192.168.0.3"),701));
                    _cts = new CancellationTokenSource();

                    var connectTask = _systemClient.ConnectAsync(ip, port);
                    var timeoutTask = Task.Delay(timeout, _cts.Token);

                    if (await Task.WhenAny(connectTask, timeoutTask) == timeoutTask)
                    {
                        throw new TimeoutException($"Connection to {ip}:{port} timed out");
                    }

                    await connectTask;
                    _stream = _systemClient.GetStream();
                    _ = ReceiveDataAsync(_cts.Token);
                }
                catch (Exception ex)
                {
                    OnError(ex);
                    throw;
                }
            }

            private async Task ReceiveDataAsync(CancellationToken ct)
            {
                try
                {
                    var buffer = new byte[4096];
                    while (!ct.IsCancellationRequested && IsConnected)
                    {
                        var bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length, ct);
                        if (bytesRead == 0) break;

                        var data = new byte[bytesRead];
                        Array.Copy(buffer, data, bytesRead);
                        OnDataReceived(new DataReceivedEventArgs(null, data));
                    }
                }
                catch (Exception ex)
                {
                    OnError(ex);
                }
            }

            public async Task SendAsync(byte[] data)
            {
                if (!IsConnected) return;

                try
                {
                    await _stream.WriteAsync(data, 0, data.Length);
                }
                catch (Exception ex)
                {
                    OnError(ex);
                }
            }

            public void Disconnect()
            {
                try
                {
                    _cts?.Cancel();
                    _systemClient?.Dispose();
                    _stream = null;
                }
                catch (Exception ex)
                {
                    OnError(ex);
                }
            }

            private void OnDataReceived(DataReceivedEventArgs e) => DataReceived?.Invoke(this, e);
            private void OnError(Exception ex) => ErrorOccurred?.Invoke(this, ex);
            public void Dispose() => Disconnect();
        }
        #endregion

        #region Common Types
        public class ClientConnectedEventArgs : EventArgs
        {
            public ClientConnection Connection { get; }

            public ClientConnectedEventArgs(ClientConnection connection)
            {
                Connection = connection;
            }
        }

        public class DataReceivedEventArgs : EventArgs
        {
            public byte[] Data { get; }
            public ClientConnection Connection { get; }

            public DataReceivedEventArgs(ClientConnection connection, byte[] data)
            {
                Connection = connection;
                Data = data;
            }

            public string GetString(Encoding encoding = null) =>
                (encoding ?? Encoding.UTF8).GetString(Data);
        }
        #endregion

        #region IP Helpers
        public static IPAddress GetPreferredLocalIP()
        {
            try
            {
                return NetworkInterface.GetAllNetworkInterfaces()
                    .Where(n => n.OperationalStatus == OperationalStatus.Up &&
                               n.NetworkInterfaceType != NetworkInterfaceType.Loopback)
                    .SelectMany(n => n.GetIPProperties().UnicastAddresses)
                    .FirstOrDefault(a => a.Address.AddressFamily == AddressFamily.InterNetwork)?.Address
                    ?? IPAddress.Any;
            }
            catch
            {
                return IPAddress.Any;
            }
        }

        public static IEnumerable<IPAddress> GetAllLocalIPs()
        {
            try
            {
                return NetworkInterface.GetAllNetworkInterfaces()
                    .Where(n => n.OperationalStatus == OperationalStatus.Up &&
                               n.NetworkInterfaceType != NetworkInterfaceType.Loopback)
                    .SelectMany(n => n.GetIPProperties().UnicastAddresses)
                    .Where(a => a.Address.AddressFamily == AddressFamily.InterNetwork)
                    .Select(a => a.Address);
            }
            catch
            {
                return new[] { IPAddress.Any };
            }
        }
        #endregion

        #region IDisposable Implementation
        private bool _disposed;

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected  void Dispose(bool disposing)
        {
            if (_disposed) return;
            _disposed = true;
        }
        #endregion
    }
}
